feat(fast-html): add AttributeMap class for automatic @attr definitions#7354
Draft
feat(fast-html): add AttributeMap class for automatic @attr definitions#7354
Conversation
AttributeMap inspects the JSON schema generated by TemplateElement for a custom element and defines @attr properties on the class prototype for all leaf-level bindings (simple {{foo}} and attribute {{bar}} paths that have no nested properties, no type, and no anyOf). - Add AttributeMap class in packages/fast-html/src/components/attribute-map.ts - Reads root properties from the Schema - Skips properties with nested 'properties', 'type', or 'anyOf' (not leaves) - Skips properties that already have an @attr or @observable accessor - Converts camelCase property names to dash-case (fooBar -> foo-bar) - Creates AttributeDefinition instances via Observable.defineProperty - Updates FASTElementDefinition.attributeLookup and propertyLookup - Integrate AttributeMap into TemplateElement (template.ts / index.ts) - Add AttributeMapOption constant and type - Add attributeMap option to ElementOptions interface - TemplateElement.options() stores attributeMap option - connectedCallback instantiates AttributeMap when attributeMap === 'all' - defineProperties() called after schema is fully populated - Add tests in attribute-map.spec.ts (browser E2E tests) - Add fixture in test/fixtures/attribute-map/ Usage: TemplateElement.options({ 'my-element': { attributeMap: 'all' }, }); Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…emplate updates Update AttributeMap.defineProperties() to also push each newly created attribute name to the existing observedAttributes array on the class. For all f-template-registered elements, registry.define() (which causes the browser to cache observedAttributes) is called AFTER defineProperties() runs, because composeAsync() waits for definition.template to be set before resolving. This creates a reliable window to mutate the array so the browser observes the dynamically-added attributes. Update tests to use element.setAttribute() inside page.evaluate instead of button clicks, testing both directions: - setAttribute() → attributeChangedCallback() → property → template re-render - property assignment → attribute reflection via DOM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…om attribute-map fixture Tests no longer need window.Observable or window.__FAST__ to verify AttributeMap behaviour. Use Object.getOwnPropertyDescriptor to check that accessor get/set was added to the prototype, and verify attribute lookup via setAttribute behaviour instead of inspecting internal registry state. Also update the beachball change type to prerelease. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…by AttributeMap Add an AttributeMapWithExistingAttrElement fixture element with a pre-defined @attr foo property (default value 'original'). After f-template processes with attributeMap: 'all', tests confirm that: - the @attr default value is preserved (accessor was not re-defined) - setAttribute() still routes through the original @attr definition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ty name in AttributeMap
Replace camelCase conversion with dash-removal: foo-bar attribute → foobar property.
Update fixture template to use {{foo-bar}} binding, update main.ts and all tests.
Fix changefile comment to use feat(fast-html): prefix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…attribute-map # Conflicts: # packages/fast-html/src/components/template.ts
…ME.md Document the dash-removal convention (foo-bar attribute → foobar property), leaf-binding eligibility rules, and usage pattern in both files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Dashes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ap fixture Template bindings should not use dashes. The attribute name can still have dashes (e.g. foo-bar on the element), but the binding itself uses foobar. Update fixture template, main.ts, and all spec descriptions accordingly. Clarify removeDashes JSDoc to reflect the design intent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
📖 Description
Adds an
AttributeMapclass to@microsoft/fast-htmlthat automatically defines@attrproperties on a custom element's class prototype based on the JSON schema generated byTemplateElement.When
attributeMap: "all"is configured for an element viaTemplateElement.options(),AttributeMapinspects the schema after template processing and creates reactive properties for all leaf bindings — simple template expressions like{{foo}}orid="{{foo-bar}}"that have no nested properties, no array type, and no child element references.Key behaviours:
foo-bar)foo-bar→foobar){{user.name}}result inuserhaving sub-propertiesin the schema and are excluded@attror@observableare left untouchedFASTElementDefinition:attributeLookupandpropertyLookupare patched soattributeChangedCallbackcorrectly delegates to the newAttributeDefinitionObservable.definePropertywith anAttributeDefinitioninstance directlyUsage
This mirrors the existing
ObserverMapintegration pattern.📑 Test Plan
13 Playwright tests were added across two spec files:
packages/fast-html/src/components/attribute-map.spec.ts— verifies accessor registration, dash-removal conversion, event handler exclusion, andFASTElementDefinitionlookup updatespackages/fast-html/test/fixtures/attribute-map/attribute-map.spec.ts— end-to-end tests verifying template re-rendering when properties are set via JavaScriptAll existing tests continue to pass.
✅ Checklist
General
$ npm run change⏭ Next Steps
observedAttributescan be updated after element registration to fully support the DOM attribute → property direction for elements registered before the template is processed